﻿// Copyright (c) Inossem. All rights reserved.
// Licensed under the LGPL-3.0 license. See LICENSE file in the project root for full license information.
using DWF.Studio.ScriptEngine.Custom;
using DWF.Studio.ScriptEngine.Debuger;
using DWF.Studio.ScriptEngine.Event;
using DWF.Studio.ScriptEngine.IPC.Msg;
using DWF.Studio.Share.Log;
using System;
using System.Activities;
using System.Activities.Debugger;
using System.Activities.Tracking;
using System.Activities.XamlIntegration;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;

namespace DWF.Studio.ScriptEngine.Implementation
{
    internal class WorkflowScriptEngine : ScriptEngineBase, ILocalScriptEngine
    {
        private WorkflowApplication _wfApplication = null;

        private WorkflowDebuger _workflowDebuger = null;

        private const string _wfActivityTypeName = "DWF.Activities.WorkFlow.InvokeXamlActivity";

        protected IThreadBlock ResultBlock
        {
            private set;
            get;
        }

        protected Exception InnerException
        {
            set;
            get;
        }

        protected Custom.ExecutionContext ExcutionContext
        {
            private set;
            get;
        }

        public override object ExcuteResult
        {
            protected set => base.ExcuteResult = value;
            get
            {
                ResultBlock.Block();

                if (InnerException != null)
                {
                    throw InnerException;
                }

                return base.ExcuteResult;
            }
        }

        public WorkflowScriptEngine(Custom.ExecutionContext excutionContext)
        {
            Id = Guid.NewGuid().ToString("N");
            ResultBlock = new ManualThreadBlock();

            ExcutionContext = excutionContext;
            ExcutionContext.Push(this);
        }

        protected void Excute(string projectDirectory, string scriptPath, Dictionary<string, object> param = null)
        {
            var script = handleArguments(scriptPath);
            MemoryStream workflowStream = new MemoryStream(ASCIIEncoding.Unicode.GetBytes(script));
            ActivityXamlServicesSettings settings = new ActivityXamlServicesSettings
            {
                CompileExpressions = true
            };

            var activityExecute = ActivityXamlServices.Load(workflowStream, settings);

            #region 默认导入参数
            Dictionary<String, Object> inputs = null;
            DynamicActivity dynamicActivity = activityExecute as DynamicActivity;
            if (dynamicActivity != null)
            {
                inputs = new Dictionary<string, object>();
                var hasScriptPath = dynamicActivity.Properties.Any(item => item.Name == "ScriptPath" && item.Type.Equals(typeof(InArgument<String>)));
                var hasProjectDirectory = dynamicActivity.Properties.Any(item => item.Name == "ProjectDirectory" && item.Type.Equals(typeof(InArgument<String>)));

                //工程目录
                if (hasProjectDirectory && !String.IsNullOrWhiteSpace(projectDirectory))
                {
                    inputs.Add("ProjectDirectory", (object)(projectDirectory + Path.DirectorySeparatorChar));
                }

                //脚本文件
                if (hasScriptPath)
                {
                    inputs.Add("ScriptPath", scriptPath);
                }

                if (param != null)  //添加参数
                {
                    foreach (var pair in param)
                    {
                        if (!inputs.ContainsKey(pair.Key))
                        {
                            inputs.Add(pair.Key, pair.Value);
                        }
                    }
                }
            }

            #endregion

            using (var syncContext = new QueueSynchronizationContext())
            {
                if (inputs != null && inputs.Count > 0)
                {
                    _wfApplication = new WorkflowApplication(activityExecute, inputs);
                }
                else
                {
                    _wfApplication = new WorkflowApplication(activityExecute);
                }

                _wfApplication.SynchronizationContext = syncContext;

                var projectId = this.ExcutionContext.GetProjectProperty("projectId");
                var assetsUri = this.ExcutionContext.GetProjectProperty("assetsUri");
                _wfApplication.Extensions.Add(new WorkflowRuntimeService(projectId, projectDirectory, scriptPath, assetsUri));
                _wfApplication.Extensions.Add(new WorkflowScriptEngineService(this.Id));
                _wfApplication.Extensions.Add(new WorkflowLogNotifyService());

                var executionLog = new WorkflowTrackingParticipant((e) =>
                {
                    SendUpwardMsg<ExecuteTrackEventArgs>(e, EMsgType.ExcuteTrack);
                });
                _wfApplication.Extensions.Add(executionLog);

                _wfApplication.Aborted = (e) =>
                {
                    SendUpwardMsg<ExecuteAbortedEventArgs>(new ExecuteAbortedEventArgs() { Reason = e.Reason.Message }, EMsgType.ExcuteAborted);
                    syncContext.Complete();
                };

                _wfApplication.OnUnhandledException = (e) =>
                {
                    InnerException = e.UnhandledException;

                    /*判断异常是否由子引擎抛出 此处存在硬编码。*/
                    bool isSubEngine = false;
                    try
                    {
                        var sourceType = e.ExceptionSource?.GetType();
                        var typeName = sourceType?.FullName;
                        if (typeName == _wfActivityTypeName)
                        {
                            var propertyName = sourceType.GetProperty("DebugFlag", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                            var value = propertyName?.GetValue(e.ExceptionSource)?.ToString();
                            Boolean.TryParse(value, out isSubEngine);
                        }
                    }
                    catch (Exception ex)
                    {
                        LogHelper.Error(ex);
                    }

                    if (!isSubEngine)
                    {
                        SendUpwardMsg<ExecuteUnhandledExceptionEventArgs>(new ExecuteUnhandledExceptionEventArgs() { ScriptPath = scriptPath, Location = e.ExceptionSource?.Id, TypeName = e.UnhandledException.GetType().FullName, StackTrace = e.UnhandledException.StackTrace, Message = e.UnhandledException.Message }, EMsgType.ExcuteUnhandledException);
                    }

                    return UnhandledExceptionAction.Terminate;
                };

                _wfApplication.Completed = (e) =>
                {
                    ExcuteResult = e.Outputs;
                    SendUpwardMsg<ExecuteCompletedEventArgs>(new ExecuteCompletedEventArgs() { TerminationMessage = e.TerminationException != null ? e.TerminationException.Message : null }, EMsgType.ExcuteCompleted);
                    syncContext.Complete();
                };

                _wfApplication.Run();

                syncContext.Run();
            }
        }

        protected void Debug(string projectDirectory, string scriptPath, Dictionary<string, object> param = null, bool isStepOver = false)
        {
            using (_workflowDebuger = new WorkflowDebuger(ExcutionContext))
            {
                var script = handleArguments(scriptPath);
                MemoryStream workflowStream = new MemoryStream(ASCIIEncoding.Unicode.GetBytes(script));
                ActivityXamlServicesSettings settings = new ActivityXamlServicesSettings
                {
                    CompileExpressions = true
                };

                var activityExecute = ActivityXamlServices.Load(workflowStream, settings);

                Dictionary<object, SourceLocation> sourceLocationMapping = new Dictionary<object, SourceLocation>();
                SourceLocationProvider.CollectMapping(activityExecute, activityExecute, sourceLocationMapping, scriptPath);

                #region 默认导入参数
                Dictionary<String, Object> inputs = null;
                DynamicActivity dynamicActivity = activityExecute as DynamicActivity;
                if (dynamicActivity != null)
                {
                    inputs = new Dictionary<string, object>();
                    var hasScriptPath = dynamicActivity.Properties.Any(item => item.Name == "ScriptPath" && item.Type.Equals(typeof(InArgument<String>)));
                    var hasProjectDirectory = dynamicActivity.Properties.Any(item => item.Name == "ProjectDirectory" && item.Type.Equals(typeof(InArgument<String>)));

                    //工程目录
                    if (hasProjectDirectory && !String.IsNullOrWhiteSpace(projectDirectory))
                    {
                        inputs.Add("ProjectDirectory", (object)(projectDirectory + Path.DirectorySeparatorChar));
                    }

                    //脚本文件
                    if (hasScriptPath)
                    {
                        inputs.Add("ScriptPath", scriptPath);
                    }

                    if (param != null)  //添加参数
                    {
                        foreach (var pair in param)
                        {
                            if (!inputs.ContainsKey(pair.Key))
                            {
                                inputs.Add(pair.Key, pair.Value);
                            }
                        }
                    }
                }
                #endregion

                using (var syncContext = new QueueSynchronizationContext())
                {
                    if (inputs != null && inputs.Count > 0)
                    {
                        _wfApplication = new WorkflowApplication(activityExecute, inputs);
                    }
                    else
                    {
                        _wfApplication = new WorkflowApplication(activityExecute);
                    }

                    _wfApplication.SynchronizationContext = syncContext;

                    var projectId = this.ExcutionContext.GetProjectProperty("projectId");
                    var assetsUri = this.ExcutionContext.GetProjectProperty("assetsUri");
                    _wfApplication.Extensions.Add(new WorkflowRuntimeService(projectId, projectDirectory, scriptPath, assetsUri));
                    _wfApplication.Extensions.Add(new WorkflowScriptEngineService(this.Id, true));
                    _wfApplication.Extensions.Add(new WorkflowLogNotifyService());

                    var executionLog = new WorkflowTrackingParticipant((e) =>
                    {
                        SendUpwardMsg<ExecuteTrackEventArgs>(e, EMsgType.ExcuteTrack);
                    });
                    _wfApplication.Extensions.Add(executionLog);

                    var debugTracking = new DebugTrackingParticipant(_workflowDebuger);
                    _workflowDebuger.FrameInfoTrack = (frameInfo) =>
                    {
                        SendUpwardMsg<DebugTrackEventArgs>(new DebugTrackEventArgs(frameInfo) { ScriptPath = scriptPath }, EMsgType.DebugTrack);
                    };
                    _workflowDebuger.StepInfoTrack = (stepInfo) =>
                    {
                        SendUpwardMsg<HitBreakpointEventArgs>(new HitBreakpointEventArgs(stepInfo), EMsgType.HitBreakpoint);
                    };
                    _wfApplication.Extensions.Add(debugTracking);
                    _workflowDebuger.SetPaths(projectDirectory, scriptPath);
                    _workflowDebuger.LoadBreakpoints();

                    _wfApplication.Aborted = (e) =>
                    {
                        SendUpwardMsg<ExecuteAbortedEventArgs>(new ExecuteAbortedEventArgs() { Reason = e.Reason.Message }, EMsgType.ExcuteAborted);
                        syncContext.Complete();
                    };

                    _wfApplication.OnUnhandledException = (e) =>
                    {
                        InnerException = e.UnhandledException;

                        /*判断异常是否由子引擎抛出 此处存在硬编码。*/
                        bool isSubEngine = false;
                        try
                        {
                            var sourceType = e.ExceptionSource?.GetType();
                            var typeName = sourceType?.FullName;
                            if (typeName == _wfActivityTypeName)
                            {
                                var propertyName = sourceType.GetProperty("DebugFlag", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                                var value = propertyName?.GetValue(e.ExceptionSource)?.ToString();
                                Boolean.TryParse(value, out isSubEngine);
                            }
                        }
                        catch (Exception ex)
                        {
                            LogHelper.Error(ex);
                        }

                        if (!isSubEngine)
                        {
                            SendUpwardMsg<ExecuteUnhandledExceptionEventArgs>(new ExecuteUnhandledExceptionEventArgs() { ScriptPath = scriptPath, Location = e.ExceptionSource?.Id, TypeName = e.UnhandledException.GetType().FullName, StackTrace = e.UnhandledException.StackTrace, Message = e.UnhandledException.Message }, EMsgType.ExcuteUnhandledException);
                        }

                        return UnhandledExceptionAction.Terminate;
                    };

                    _wfApplication.Completed = (e) =>
                    {
                        ExcuteResult = e.Outputs;
                        SendUpwardMsg<ExecuteCompletedEventArgs>(new ExecuteCompletedEventArgs() { TerminationMessage = e.TerminationException != null ? e.TerminationException.Message : null }, EMsgType.ExcuteCompleted);
                        syncContext.Complete();
                    };
                    _wfApplication.Run();

                    syncContext.Run();
                }
            }
        }

        public override void Cancel()
        {
            SendUpwardMsg<EventArgs>(EventArgs.Empty, EMsgType.ExcuteEnd);
            ExcutionContext.Pop(this);
            ResultBlock.Continue();
        }

        public override void ExecuteScript(string projectDirectory, string scriptPath)
        {
            ExecuteScript(projectDirectory, scriptPath, null);
        }

        public override void ExecuteScript(string projectDirectory, string scriptPath, Dictionary<string, object> param)
        {
            this.ResultBlock.Reset();
            this.InnerException = null;
            this.ExcuteResult = null;

            Thread thread = new Thread(new ThreadStart(() =>
            {
                try
                {
                    SendUpwardMsg<ExecuteStartEventArgs>(new ExecuteStartEventArgs(false, "Begin ExecuteScript"), EMsgType.ExcuteStart);
                    Excute(projectDirectory, scriptPath, param);
                }
                catch (Exception ex)
                {
                    InnerException = ex;
                    SendUpwardMsg<ExecuteUnhandledExceptionEventArgs>(new ExecuteUnhandledExceptionEventArgs() { ScriptPath = scriptPath, TypeName = ex.GetType().FullName, StackTrace = ex.StackTrace, Message = ex.Message }, EMsgType.ExcuteUnhandledException);
                }
                finally
                {
                    SendUpwardMsg<EventArgs>(EventArgs.Empty, EMsgType.ExcuteEnd);
                    ExcutionContext.Pop(this);
                    ResultBlock.Continue();
                }
            }));

            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;
            thread.Priority = ThreadPriority.BelowNormal;
            thread.Start();
        }

        public override void DebugScript(string projectDirectory, string scriptPath)
        {
            DebugScript(projectDirectory, scriptPath, null);
        }

        public override void DebugScript(string projectDirectory, string scriptPath, Dictionary<string, object> param)
        {
            this.ResultBlock.Reset();
            this.InnerException = null;
            this.ExcuteResult = null;

            Thread thread = new Thread(new ThreadStart(() =>
            {
                try
                {
                    //SendUpwardMsg<EventArgs>(EventArgs.Empty, EMsgType.ExcuteStart);  //Oliver shield 2021-06-21
                    SendUpwardMsg<ExecuteStartEventArgs>(new ExecuteStartEventArgs(false, "Begin DebugScript"), EMsgType.ExcuteStart);
                    Debug(projectDirectory, scriptPath, param);
                }
                catch (Exception ex)
                {
                    InnerException = ex;
                    SendUpwardMsg<ExecuteUnhandledExceptionEventArgs>(new ExecuteUnhandledExceptionEventArgs() { ScriptPath = scriptPath, TypeName = ex.GetType().FullName, StackTrace = ex.StackTrace, Message = ex.Message }, EMsgType.ExcuteUnhandledException);
                }
                finally
                {
                    SendUpwardMsg<EventArgs>(EventArgs.Empty, EMsgType.ExcuteEnd);

                    ExcutionContext.Pop(this);
                    ResultBlock.Continue();
                }
            }));

            thread.SetApartmentState(ApartmentState.STA);
            thread.IsBackground = true;
            thread.Priority = ThreadPriority.BelowNormal;
            thread.Start();
        }

        protected void SendUpwardMsg<T>(T datum, EMsgType msgType)
        {
            ExcutionContext.SendMsg(datum, msgType, this.Id, EEngineType.WorkflowScriptEngine);
        }

        private const string XamlXmlnsRoot = "http://schemas.microsoft.com/netfx/2009/xaml/activities";
        private const string XamlXmlnsX = "http://schemas.microsoft.com/winfx/2006/xaml";

        private string handleArguments(string scriptPath)
        {
            XmlDocument xmldoc = new XmlDocument();
            xmldoc.Load(scriptPath);
            var xmlnsm = new XmlNamespaceManager(xmldoc.NameTable);
            xmlnsm.AddNamespace("xaroot", XamlXmlnsRoot);
            xmlnsm.AddNamespace("x", XamlXmlnsX);

            var activity_node = xmldoc.SelectSingleNode("/xaroot:Activity", xmlnsm);
            if (activity_node != null)
            {
                var members_node = activity_node.SelectSingleNode("x:Members", xmlnsm);
                if (members_node == null)
                {
                    members_node = xmldoc.CreateElement("x", "Members", XamlXmlnsX);
                    activity_node.AppendChild(members_node);
                }

                var projectDirectory_node = members_node.SelectSingleNode("x:Property[@Name=\"ProjectDirectory\"]", xmlnsm);
                if (projectDirectory_node == null)
                {
                    var property_node = xmldoc.CreateElement("x", "Property", XamlXmlnsX);
                    property_node.SetAttribute("Name", "ProjectDirectory");
                    property_node.SetAttribute("Type", "InArgument(x:String)");

                    members_node.AppendChild(property_node);
                }

                var scriptPath_node = members_node.SelectSingleNode("x:Property[@Name=\"ScriptPath\"]", xmlnsm);
                if (scriptPath_node == null)
                {
                    var property_node = xmldoc.CreateElement("x", "Property", XamlXmlnsX);
                    property_node.SetAttribute("Name", "ScriptPath");
                    property_node.SetAttribute("Type", "InArgument(x:String)");

                    members_node.AppendChild(property_node);
                }
            }

            return xmldoc.InnerXml;
        }

        public override void LoadDepencyAssemblies(string[] assemblyNames)
        {
            AppDomain.CurrentDomain.Load("System.Activities, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
            AppDomain.CurrentDomain.Load("System.Activities.Core.Presentation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
            AppDomain.CurrentDomain.Load("Microsoft.Activities.Extensions, Version=2.0.6.9, Culture=neutral, PublicKeyToken=23b0c89d0d5ad43f");

            base.LoadDepencyAssemblies(assemblyNames);
        }

        public override void DebugResume()
        {
            _workflowDebuger?.Resume();
        }

        public override void DebugStepInto()
        {
            _workflowDebuger?.StepInto();
        }

        public override void DebugStepOut()
        {
            _workflowDebuger?.StepOut();
        }

        public override void DebugStepOver()
        {
            _workflowDebuger?.StepOver();
        }

        public override void CallFrameInfo()
        {
            _workflowDebuger?.CallFrameInfo();
        }

        public void RefreshBreakpoints(string scriptPath)
        {
            if (_workflowDebuger?.CurrentScriptPath == scriptPath)
            {
                _workflowDebuger?.LoadBreakpoints();
            }
        }
    }
}
